package com.wang.tool.proxy;

import com.alibaba.fastjson.JSONObject;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;

/**
 * 业务代理类
 *
 * @Author wangshaopeng
 * @Date 2020-08-06
 */
public class FunctionProxy<P, R> implements Function<P, R> {

    /**
     * 原始业务
     */
    private Function<P, R> delegate;

    /**
     * 切面列表
     */
    private List<Cut> cuts;

    /**
     * 构建代理
     *
     * @param delegate 原始业务
     * @param <P>
     * @param <R>
     * @return
     */
    public static <P, R> FunctionProxy<P, R> buildProxy(Function<P, R> delegate) {
        FunctionProxy<P, R> proxy = new FunctionProxy<>();
        proxy.setDelegate(delegate);
        return proxy;
    }

    /**
     * 构建代理
     *
     * @param delegate 原始业务
     * @param cuts     切面列表
     * @param <P>
     * @param <R>
     * @return
     */
    public static <P, R> FunctionProxy<P, R> buildProxy(Function<P, R> delegate, List<Cut> cuts) {
        FunctionProxy<P, R> proxy = new FunctionProxy<>();
        proxy.setDelegate(delegate);
        proxy.setCuts(cuts);
        return proxy;
    }


    @Override
    public R apply(P param) {
        return new InvokeChain().chain(param, new JSONObject());
    }


    /**
     * 添加切面
     *
     * @param cuts
     * @return
     */
    public FunctionProxy<P, R> addCuts(Cut... cuts) {
        if (this.cuts == null) {
            this.cuts = new ArrayList<>();
        }
        if (cuts != null) {
            for (Cut cut : cuts) {
                check(cut);
            }
            this.cuts.addAll(Arrays.asList(cuts));
            sortCuts();
        }
        return this;
    }

    /**
     * 删除切面
     *
     * @param cuts
     * @return
     */
    public FunctionProxy<P, R> removeCuts(Cut... cuts) {
        if (cuts != null && this.cuts != null) {
            this.cuts.removeAll(Arrays.asList(cuts));
            sortCuts();
        }
        return this;
    }


    /**
     * 排序切面
     */
    private void sortCuts() {
        if (cuts == null) {
            return;
        }
        //排序
        cuts.sort((a, b) -> {
            if (a == null && b == null) {
                return 0;
            }
            if (a == null) {
                return -1;
            } else if (b == null) {
                return 1;
            }
            if (Objects.equals(a.level(), b.level())) {
                return a.order() - b.order();
            }
            return a.level() - b.level();
        });
    }


    public List<Cut> getCuts() {
        return cuts;
    }

    public Function<P, R> getDelegate() {
        return delegate;
    }

    public void setDelegate(Function<P, R> delegate) {
        this.delegate = delegate;
    }

    public void setCuts(List<Cut> cuts) {
        this.cuts = cuts;
        if (this.cuts != null) {
            this.cuts.forEach(this::check);
        }
        sortCuts();
    }

    /**
     * 检查cut有效性
     *
     * @param cut
     */
    private void check(Cut cut) {
        if (cut == null) {
            throw new IllegalArgumentException("cut can not be null");
        }
        if (!BeforeCut.class.isAssignableFrom(cut.getClass())
                && !AfterCut.class.isAssignableFrom(cut.getClass())
                && !AroundCut.class.isAssignableFrom(cut.getClass())) {
            throw new IllegalArgumentException("cut must be whiteType, cutType:" + cut.getClass().getName());
        }
    }

    /**
     * 一次调用链
     *
     * @Author wangshaopeng
     * @Date 2020-08-06
     */
    public class InvokeChain {

        /**
         * 当前执行下标
         */
        private int currentIndex = 0;

        /**
         * 切面数组 非空
         */
        private final Cut[] cuts = getCuts() == null ? new Cut[0] : getCuts().toArray(new Cut[0]);


        private InvokeChain() {
        }

        /**
         * 继续
         *
         * @param param  入参
         * @param runCtx 执行环境 非空
         * @return
         */
        public R chain(P param, JSONObject runCtx) {
            if (this.currentIndex == this.cuts.length) {
                return delegate == null ? null : delegate.apply(param);
            }
            Cut cut = cuts[currentIndex++];
            return cut == null ? chain(param, runCtx) : (R) cut.invoke((FunctionProxy<Object, Object>.InvokeChain) this, param, runCtx);
        }

    }
}
